package processing.test.sakura;

import processing.core.*; 
import processing.data.*; 
import processing.event.*; 
import processing.opengl.*; 

import oscP5.*; 
import netP5.*; 
import java.io.*; 

import java.util.HashMap; 
import java.util.ArrayList; 
import java.io.File; 
import java.io.BufferedReader; 
import java.io.PrintWriter; 
import java.io.InputStream; 
import java.io.OutputStream; 
import java.io.IOException; 

public class Sakura extends PApplet {

/* Sakura Conducer */




float gm = 1.60f;
float gr = 0.60f;
OscP5 oscP5;
PFont font;
File bloomSrc = new File("/mnt/external_sd/bloom.i");

int[] flowers = new int[16]; 
int[] facets = new int[16];
float[] ranges = new float[8];
String[] facetName = new String[16];
String bloom[]=new String[8];
int screenX;
int screenY;

//OSC VARIABLE INPUTS
String pmsg = ""; // previous OSC message
String cmsg = ""; // current OSC Message
String fmsg = ""; // nexpected OSC Message

float oscX, oscY = 0.0f;

public void setup() {
  //    size (720,720);
 
  // if (frame != null){frame.setResizable(true);} //<>//
  frameRate(27);
  /* create a new instance of oscP5 using a multicast socket. */
  oscP5 = new OscP5(this, "239.0.0.1", 7777);
  //background(0);
  textFont(createFont("sans-serif", 27));

  //initialize the controller state banks  
  for (int i=0; i< flowers.length; i++) {
    flowers[i] = 0;
  }
  for (int i=0; i< ranges.length; i++) {
    ranges[i] = 0.5f;
  }
  for (int i=0; i< facets.length; i++) {
    facets[i] = 0;
  }
}
public void facetSetup() {
  //  facetName=loadStrings("facetNames.txt");

  dfSetup();
  tvSetup();
  sfSetup();
}
public void stage() {
  //  tvFacet();
  if (tripout) {
    tvFacet();
  }
  if (facetColor) {

    colorMode(RGB, 255, 255, 255);
  } else {
    colorMode(HSB, 1, 1, 1, 1);

    fill( ranges[4], ranges[5], ranges[6], ranges[7]);
    stroke( ranges[4]*gm, ranges[5]*gm, ranges[6]*gm, ranges[7]*gm);
  }
  //  text(pmsg, width/2, height/2);
  //  text(cmsg, width/2, height/3);
  //  text(fmsg, width/2, height/4);
  if (sf){
    sfFacet();
  }
  if (testFacet){
    tFacet();
  }
  if (df) {
    //  background(0);
    dfFacet();
  }
}

boolean sett = false;
boolean rd = false;
boolean facetColor = false;
public void setting() {
  boolean manual=true;  // use manual bloom to set bloom value manually for testing
  if (manual) {
    bloom[0]="0";
    bloom[1]="A";
    bloom[2]="0";
    bloom[3]="0";
    bloom[4]="0";
    bloom[5]="Ready 101 B";
    bloom[6]="localhost";
    bloom[7]="30000";
  }
  else {    
    bloom=loadStrings(bloomSrc);
  }

  translate(width/2, height/2);
  // name the facets in an array

  //  rotate(atan2(sq(width),sq(height)));
  //  beginShape();
  //  vertex(-sq(width),-sq(height));
  //  vertex(sq(width),-sq(height));
  //  vertex(sq(width),sq(height));
  //  vertex(-sq(width),sq(height));
  //  vertex(-sq(width),-sq(height));
  //  endShape();  
  background(0);
  facetSetup();
}

public void draw() {
  //  fill(9,9);


  if (!sett) {
    setting();
    sett=true;
  }
  //fill(255,96);
  if (rd) {
    System.gc();
    redraw();
    rd=false;
  }
    stage();
 //   sfFacet();

}


public void mousePressed() {

  if (mouseY<=height/2) {

    /* create a new OscMessage with an address pattern, in this case /test. */
    OscMessage myOscMessage = new OscMessage("/above");

    /* add a value (an integer) to the OscMessage */
    myOscMessage.add(millis());

    /* send the OscMessage to the multicast group. 
     * the multicast group netAddress is the default netAddress, therefore
     * you dont need to specify a NetAddress to send the osc message.
     */
    oscP5.send(myOscMessage);
  } else {

    /* create a new OscMessage with an address pattern, in this case /test. */
    OscMessage myOscMessage = new OscMessage("/below");

    /* add a value (an integer) to the OscMessage */
    myOscMessage.add(millis());

    /* send the OscMessage to the multicast group. 
     * the multicast group netAddress is the default netAddress, therefore
     * you dont need to specify a NetAddress to send the osc message.
     */
    oscP5.send(myOscMessage);
  }
}

public void keyPressed() {
  switch(key) {
  case 'T':
    testFacet=!testFacet;
    break;
  case 'D':
    df=!df;
    break;
  case 'R':
    rd=!rd;
    break;
  }
}/* Sakura Conducer */


/*Dragon Fractal 2013,*/


boolean sf=false; 
public void sfSetup() {
}

public void sfFacet() {
    translate(oscX, oscY);

  float thisRad = dRad;
  float qGrow;
  float qColor;
  background(0);
  stroke(0);
  frame=frame+1;
  ArrayList getIt = qRate(frame);
  qGrow = ((Float)getIt.get(0));
  thisRad = thisRad+qGrow;
  ellipse((dStage/2), (dStage/2), thisRad, thisRad);
  qColor=((Float)getIt.get(1));
  fill(qColor); 
  if (thisRad > dStage) {
    frame = 0;
    //      noLoop();
  }
}
boolean df = false;
public void dfSetup() {
  f = new DragonFractal(50, uSizeMin, .6f, PI / dDrive1, PI / dDrive2);  //60,1,.6,3,12
  //f.setStrokeRange(color( 30, 180, 45, 255), color(  0, 0, 0, 55));
  //f.setFillRange(  color( 40, 20, 50, 255), color(100, 120, 250, 55));
  f.setStartLocation(width / 2, height / 2);
  f.setDrawOrder(Fractal.LAST);
  f.setRenderMode(DragonFractal.ELLIPSE);
}
public void dfFacet() {
  frameRate(30);
  if (rd) {  
    pushMatrix();
    f = new DragonFractal(50, uSizeMin, .6f, PI / dDrive1, PI / dDrive2);  //60,1,.6,3,12};
    popMatrix();
  }  
  moveX -= PApplet.parseInt(3*sin(PApplet.parseFloat(frameCount/30)));
  moveY -= PApplet.parseInt(4*sin(PApplet.parseFloat(frameCount/30)));
  // f.setStrokeRange(color( 30*.004, 180*.004,  45*.004, 255*.004), color(  ranges[8],   ranges[7],   ranges[6],  ranges[5]));
  //f.setFillRange(  color( ranges[4],  ranges[5],  ranges[6], ranges[7]), color(100*.004, 120*.004, 250*.004,  55*.004));
  f.setOffsetAngles(map(moveX, 0, width, 0, TWO_PI), 
  map(moveY, 0, height, 0, TWO_PI));
  //  f.setOffsetAngles(map(mouseX, 0,  width, 0, TWO_PI),
  //                    map(mouseY, 0, height, 0, TWO_PI));
  f.followMouseAngle();
  f.draw((ranges[0]));
}
/*Dragon Fractal, Zeth D, 2013,*/


/*test facet*/

boolean testFacet = false;


public void tFacet() {
  cmsg=bloom[5]+", recived message: "+oscX+" "+oscY
    +"\n\n"+"fader:"+PApplet.parseInt(map(second(), 0, 59, 0, 7))+" "+ranges[PApplet.parseInt(map(second(), 0, 59, 0, 7))];

  if (pmsg!=cmsg) {
    background(0);
  }
  text(cmsg, width/2, height/2);
  pmsg=cmsg;
}
public void tvSetup() {
  //  size(768, 768);
  smooth();
  frameRate(36);
  ellipseMode(CENTER);
  colorMode(HSB);
  strokeWeight(0.3f);
  stroke(255, 64);
  nDotsInv = 1.5f / nDots;
  dotRotate = TWO_PI * nDotsInv;
  nDotsInvDiv8 = nDotsInv / 8.0f;
}
boolean tripout=false;
public void tvFacet() {
  float fromCenter = 7;
  float radius = 10.0f;

  translate(oscX, oscY);
  rotate(rads);
  rads += inc;
  hue += 3;

  if (rads >= TWO_PI) {
    rads -= TWO_PI;
  }

  if (hue > 255) {
    hue -= 255;
  }

  for (int i = 0; i < nDots * nOrbits; i++) {
    float diameter = radius * PI;
    float c = (hue + i * 7) % 255;

    fill(c, 255, 255-frameRate%frameCount, frameCount%frameRate);
    ellipse(0, fromCenter, diameter, diameter);
    radius = radius + radius * nDotsInvDiv8;
    fromCenter += radius * nDotsInv;
    rotate(dotRotate);
  }
}

/*shapeFreq*/




/*shap freq */

float dRad = 30;
float dFPS = 30;
int dStage;

float uColor = 255;
float uDuration=2;
float uDegree=10;
float uDecay=.1f; //as a percentage of uDuration
float uAfterimage;

//initialize

float frame = 0;
boolean testOutput= false;
boolean toggleLoop = true;

public ArrayList qRate (float frame){
  ArrayList result = new ArrayList();
  float thisFrame;
  float qProgress;
  float qFade=0;
  thisFrame =(((1/uDuration)/dFPS)*frame);
  float rad = pow(thisFrame,uDegree);
  result.add((float) (rad*(dStage-dRad)+dRad));
  if (1-thisFrame<=uDecay){
    println("decay "+ thisFrame);
    qFade=((1/uDecay)*(1-thisFrame));
    result.add((float) (qFade*uColor));
    println ("fade: " + qFade);
  }else{
    result.add((float) (uColor));
  }
  if (testOutput){
    println ("frame: " + frame);
  }
  return result;
}

public void facetClear() {
  for (int i=0; i< facets.length; i++) {
    facets[i] = 0;
  }
}
  /*Dragon Fractal, Zeth D, 2013,*/

  DragonFractal f;
  float uSizePulse = 4; // range 1-10
  int uSizeMin = 3; // range 2-6
  int dDrive1=3;
  int dDrive2=12;
  int moveX, moveY=720;

  class DragonFractal extends Fractal {

    int renderMode;

    DragonFractal(int startR, int endR, float dx, float d1, float d2) {
      super(startR, endR);
      drawFirst = false;
      children = new Recursor[] { 
        new Recursor(-2*dx, 2*dx, 2*sqrt(2)*dx - 1, d1), 
        new Recursor( 2*dx, -2*dx, 2*sqrt(2)*dx - 1, d2)
        };
      }

      public void init() {
        strokeWeight(1);
        rectMode(RADIUS);
        ellipseMode(RADIUS);
      }

    public void pattern(float r) {
      switch(renderMode) {
      case RECT:
        rect(0, 0, r, r);
        break;
      case ELLIPSE:
        ellipse(0, 0, r, r);
        break;
      case TRIANGLE:
        triangle(-r, r / sqrt3, r, r / sqrt3, 0, -2 * r / sqrt3);
        break;
      }
    }

    static final int RECT     = 0, 
    ELLIPSE  = 1, 
    TRIANGLE = 2;
    public void setRenderMode(int renderMode) {
      this.renderMode = renderMode;
    }

    public void setOffsetAngle(int i, float t) {
      children[i].t = t;
    }

    public void setOffsetAngles(float t0, float t1) {
      children[0].t = t0;
      children[1].t = t1;
    }

    public void setOffsetAngles(float[] t) {
      children[0].t = t[0];
      children[1].t = t[1];
    }
  }
  abstract class Fractal {

    // determine recursion behaviour
    Recursor[] children;

    // determine location
    int   startX;
    int   startY;

    // determine dimension
    int   startR;
    int     endR;

    // determines orientation
    float startT;

    // determines whether to draw before recursing or vice-versa
    boolean drawFirst;
    static final boolean FIRST = true, 
    LAST  = false;

    // determine color traits
    boolean doStroke, doFill;
    int startFill, endFill;
    int startStroke, endStroke;

    // useful constants
    float sqrt2 = sqrt(2);
    float sqrt3 = sqrt(3);

    // Base Constructor
    Fractal(int startR, int endR) {
      setRadiusLimits(startR, endR);
    }

    // Method to control fractal
    public void draw(float gfactor) {
      init();
      pushMatrix();
      translate(startX, startY);
      rotate(startT);
      startR=startR+PApplet.parseInt(((gfactor*10)*sin (PApplet.parseFloat(frameCount)/40)));
      fractal(0, 0, startR, 0);
      popMatrix();
    }

    // Called once before recursion
    public abstract void init();

    // Pattern definition
    public abstract void pattern(float r);

    // Run the show
    public void fractal(float x, float y, float r, float t) {
      if (drawFirst) {
        display(x, y, r, t);
        recurse(x, y, r, t);
      } 
      else {
        recurse(x, y, r, t);
        display(x, y, r, t);
      }
    }

    // Let's try this again
    public void recurse(float x, float y, float r, float t) {
      for (Recursor child: children) {
        float dr = child.r;
        float newR = r*dr;
        if (newR > endR) {
          float dx = child.x;
          float dy = child.y;
          float dt = child.t;
          float c = cos(t);
          float s = sin(t);
          fractal(x + r*(dx*c - dy*s), y + r*(dx*s + dy*c), newR, t + dt);
        }
      }
    }

    // Draw each piece in its proper location and orientation
    public void display(float x, float y, float r, float t) {
      pushMatrix();
      translate(x, y);
      rotate(t);
      applyColors(r);
      pattern(r);
      popMatrix();
    }

    // Set fill and stroke based on dimension
    public void applyColors(float r) {
      float q = r / startR;
      if (doFill) fill(lerpColor(startFill, endFill, q));
      if (doStroke) stroke(lerpColor(startStroke, endStroke, q));
    }

    // Methods to control properties
    public void setStartLocation(int startX, int startY) {
      this.startX = startX;
      this.startY = startY;
    }
    public void setStartingAngle(float startT) {
      this.startT = startT;
    }
    public void followMouseAngle() {
      setStartingAngle(atan2(startY - oscY, startX - oscX));
    }
    public void setRadiusLimits(int startR, int endR) {
      this.startR = startR;
      this.endR = endR;
    }
    public void setDrawOrder(boolean drawFirst) {
      this.drawFirst = drawFirst;
    }

    // Methods to control color
    public void setFillRange(int startFill, int endFill) {
      this.startFill = startFill;
      this.endFill = endFill;
      doFill = true;
    }
    public void setStrokeRange(int startStroke, int endStroke) {
      this.startStroke = startStroke;
      this.endStroke = endStroke;
      doStroke = true;
    }
    public void noStroke() {
      doStroke = false;
    }
    public void noFill() {
      doFill = false;
    }
  }
  class Recursor {

    float x, y, r, t;

    Recursor(float[] params) {
      x = params[0];
      y = params[1];
      r = params[2];
      t = params[3];
    }

    Recursor(float x, float y, float r, float t) {
      this.x = x;
      this.y = y;
      this.r = r;
      this.t = t;
    }

    public String toString() {
      return "[" + x + ", " + y + ", " + r + ", " + t + "]";
    }
  }
  /*Dragon Fractal, Zeth D, 2013,*/
  
  /* trip vortex */

float hue = 0.0f;
float rads = 0.0f;
float inc = 0.025f;
int nDots = 25;
int nOrbits = 10;
float nDotsInv;
float dotRotate;
float nDotsInvDiv8;
/* incoming osc message are forwarded to the oscEvent method. */

/* these fetch routines could be expanded to return an array with other info
 such as string info like "on" and... ??  */
String addr="";
public float oscFloatFetch (OscMessage theOscMessage, int which) {
  float theValue=theOscMessage.get(which).floatValue();
  return theValue;
}

public void oscEvent(OscMessage theOscMessage) {
  addr = theOscMessage.addrPattern();

  /* print the address pattern and the typetag of the received OscMessage 
   then route to the proper handler by sakura control type */

  print("### received an osc message.");
  println(" addrpattern: "+addr);
  println(" typetag: "+theOscMessage.typetag());
  // FlowerState
  if (addr.indexOf("FlowerState")>-1) {
    println("changing the flower activation state");
    flowerState(theOscMessage);
  }
  if (flowers[PApplet.parseInt(bloom[0])]==1) { 
    // FacetState  
    if (addr.indexOf("FacetState")>-1) {
      println("changing the facet activation state");
      facetState(theOscMessage);
    }
    // xy1  
    if (addr.indexOf("xy1")>-1) {
      println("reading the XY slider");
      sendXY(theOscMessage);
    }
    // rotary1-4, slider1-4
    if (addr.indexOf("fader")>-1) {
      print("fading out...");
      sendFade(theOscMessage);
    }
    //disrupt  
    if (addr.indexOf("color")>-1) {
      sendDisrupt(theOscMessage);
    }
    //facet selection
    if (addr.indexOf("FacetState")>-1) {
      println("toggle facet state");
      facetState(theOscMessage);
    }
  }
} // end oscEvent

public void sendXY(OscMessage theOscMessage) {
  float xValue=oscFloatFetch(theOscMessage, 0);
  float yValue=oscFloatFetch(theOscMessage, 1);
  println ("X: "+xValue+ " Y: " +yValue);
  oscX=xValue*width;
  mouseX=PApplet.parseInt(xValue*width);
  oscY=yValue*width;
  mouseY=PApplet.parseInt(xValue*height);
  //  now hook up the data
}  /* end sendXY */

public void sendDisrupt(OscMessage theOscMessage) {
  String buffer[] = split(addr, "/");
  int colorValue = PApplet.parseInt(buffer[3]);    
  println ("Disrupt with hue: "+colorValue);
}  /* end sendDisrupt */

public void sendFade(OscMessage theOscMessage) {
  float faderValue=oscFloatFetch(theOscMessage, 0);
  String buffer2[] = split(addr, "/");
  //s
  int thisFader = PApplet.parseInt(buffer2[3])-1;
  ranges[thisFader]=faderValue;
  println ("Fader "+thisFader+ " set at " +faderValue);
  //  flowers[thisFlower]=int(toggleValue);
}  /* end sendFade */

public void flowerState(OscMessage theOscMessage) {
  String msg1="";
  float toggleValue=oscFloatFetch(theOscMessage, 0);
  String buffer[] = split(addr, "/");

  int thisFlower = (PApplet.parseInt(buffer[4])-1);
  if (toggleValue>0) {
    msg1="ON";
  } else {
    msg1="OFF";
  }
  println ("Flower "+thisFlower+ " " +msg1);
  flowers[thisFlower]=PApplet.parseInt(toggleValue);
}  /* end flowerState */

public void facetState(OscMessage theOscMessage) {
  String msg1="";
  float toggleValue=oscFloatFetch(theOscMessage, 0);

  String buffer[] = split(addr, "/");

  int thisFacet = (PApplet.parseInt(buffer[4])-1);
  if (toggleValue>0) {
    msg1="ON";
  } else {
    msg1="OFF";
  }
  println ("Facet "+thisFacet+ ", " +facetName[thisFacet]+" "+msg1);
  facets[thisFacet]=PApplet.parseInt(toggleValue-1);
  facetTalk(thisFacet, toggleValue);
}  /* end facetState */

//if (addr.indexOf("FlowerState/1/")>-1) {

/*
  pmsg=(" addrpattern: "+theOscMessage.addrPattern()+"recieved by conducer started "+millis()+ "milliseconds ago");
 fmsg=" "+(millis()-theOscMessage.get(0).intValue());
 cmsg=" "+theOscMessage.get(0).intValue();
 println(cmsg);
 */

public void facetTalk(int launch, float state ) {
  switch (launch) {
  case 0:
      println("dragon fractal facet switched "+state);
    if (state==1.0f){
      df=true;
    }else{
      df=false;
    }
    break;
  case 2:
      println("shapeFreq facet switched "+state);
    if (state==1.0f){
      sf=true;
    }else{
      sf=false;
    }
    break;  
  case 15:
    println("test facet switched "+state);
    if (state==1.0f){
      testFacet=true;
    }else{
      testFacet=false;
    }
      
    
  default:
    break;
  }
}

//end oscTransponder


  public int sketchWidth() { return displayWidth; }
  public int sketchHeight() { return displayHeight; }
  public String sketchRenderer() { return OPENGL; }
}
